﻿using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace TypeScriptJson.Generator
{
	public class ObjectTypeMapping: TypeMapping
	{
		public JsonObjectContract Contract { get; private set; }
		public ObjectTypeMapping BaseTypeMapping { get; set; }
		public IEnumerable<TypeMapping> GenericParameters { get; private set; }

		public struct PropertyInfo
		{
			public JsonProperty Property;
			public TypeMapping Mapping;
		}

		public override string GetTypeNameInternal()
		{
			var typeName = UnderlyingType.Name;
			if (UnderlyingType.IsGenericType)
				typeName = typeName.Substring(0, typeName.IndexOf('`'));
			if (GenericParameters != null)
				typeName = GenericParameters.Aggregate(typeName + "$", (current, parameter) => current + (parameter.TypeName + "$"));
			return typeName;
		}

		protected override string ModuleNameInternal
		{
			get { return UnderlyingType.Namespace; }
		}

		public override bool IsGenerateDeclaration
		{
			get { return true; }
		}

		public ObjectTypeMapping(JsonObjectContract contract) : base(contract.UnderlyingType)
		{
			Contract = contract;
		}

		protected override void ProcessInternal(GeneratorContext context)
		{
			if (UnderlyingType.IsGenericType)
				GenericParameters = (from parameter in UnderlyingType.GetGenericArguments()
				                     select context.MapType(parameter)).ToList();

			if (!UnderlyingType.IsValueType)
			{
				var baseType = UnderlyingType.BaseType;
				if (baseType != null && baseType != typeof (object))
					BaseTypeMapping = (ObjectTypeMapping) context.MapType(baseType);
			}

			Properties = (from property in Contract.Properties
						  where !property.Ignored && property.Readable && property.DeclaringType == UnderlyingType
						  select new PropertyInfo { Property = property, Mapping = context.MapType(property.PropertyType) })
				.ToList();
		}

		public IEnumerable<PropertyInfo> Properties { get; protected set; }

		public override void WriteTypeDeclaration(TypeScriptCodeWriter codeWriter, GeneratorContext context)
		{
			codeWriter.WriteStartInterface(export: true);
			codeWriter.Writer.Write(TypeName);
			if (BaseTypeMapping != null)
			{
				codeWriter.WriteExtends();
				BaseTypeMapping.WriteTypeReference(codeWriter, ModuleName);
			}
			codeWriter.Writer.Write(" ");
			codeWriter.WriteStartBlock(isAtStartOfLine: false);

			foreach (var property in Properties)
			{
				codeWriter.WriteStartField(
					property.Property.PropertyName,
					isOptional: property.Property.Required == Required.Default &&
					            ((property.Property.NullValueHandling
					              ?? context.SerializerSettings.NullValueHandling) == NullValueHandling.Ignore
					             || ((property.Property.DefaultValueHandling
					                  ?? context.SerializerSettings.DefaultValueHandling) & DefaultValueHandling.Ignore) ==
					             DefaultValueHandling.Ignore));
				codeWriter.Writer.Write(": ");
				property.Mapping.WriteTypeReference(codeWriter, ModuleName);
				codeWriter.Writer.WriteLine(";");
			}

			codeWriter.WriteEndInterface();
		}
	}
}